﻿using System;
using System.Collections.Generic;
using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Threading;
using System.Diagnostics;

namespace ServerSocket
{
    public enum MessageType
    {
        beat,
        message
    }

    class Program
    {
        static Dictionary<string, GuestInfo> m_Guests;
        static Byte[] flushBuffer;
        static List<string> m_SocketsWaitForRemoved;
        static List<GuestInfo> m_WaitingForSendMessage;
        static Stopwatch m_Watch;
        static int m_BreakConnectSpaceTime = 100000;
        static int m_BeatSapceTime = 9000;
        static void Main(string[] args)
        {
            Console.WriteLine("ServerStart");
            m_Watch = new Stopwatch();
            IPAddress adress;
            IPEndPoint port;
            m_SocketsWaitForRemoved = new List<string>();
            m_Guests = new Dictionary<string, GuestInfo>();
            m_WaitingForSendMessage = new List<GuestInfo>();
            flushBuffer = new Byte[1024 * 1024 * 2];

            adress = IPAddress.Any;
            Console.WriteLine(adress);
            port = new IPEndPoint(adress, 51000);

            Console.WriteLine("CreateSocket");
            Socket socketWatch = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
            socketWatch.Bind(port);
            socketWatch.Listen(10);
            Thread comunication = new Thread(Comunication);
            comunication.Start();
            while (true)
            {
                Socket socketCommunication = socketWatch.Accept();
                socketCommunication.Blocking = false;
                Console.WriteLine(socketCommunication.RemoteEndPoint);
                lock (m_Guests)
                {
                    GuestInfo newGuestInfo = new GuestInfo();
                    newGuestInfo.timeStamp = m_Watch.ElapsedMilliseconds;
                    newGuestInfo.socket = socketCommunication;
                    newGuestInfo.buffer = new List<byte>();

                    m_Guests.Add(socketCommunication.RemoteEndPoint.ToString(), newGuestInfo);
                }
            }
        }

        private static void Comunication()
        {
            m_Watch.Start();
            while (true)
            {
                lock (m_Guests)
                {
                    Int64 curTime = m_Watch.ElapsedMilliseconds;
                    m_WaitingForSendMessage.Clear();
                    foreach (GuestInfo linkedGuest in m_Guests.Values)
                    {
                        GuestInfo guest = linkedGuest;
                        Int64 beatSpaceTime = curTime - linkedGuest.timeStamp;
                        if (beatSpaceTime > m_BreakConnectSpaceTime)
                        {
                            m_SocketsWaitForRemoved.Add(guest.socket.RemoteEndPoint.ToString());
                            linkedGuest.socket.Shutdown(SocketShutdown.Both);
                            linkedGuest.socket.Close();
                            continue;
                        }else if( !guest.beatDirty && beatSpaceTime > m_BeatSapceTime)
                        {
                            SendBeatData(guest);
                        }

                        try
                        {
                            int recieveLength = 0;
                            try
                            {
                                recieveLength = linkedGuest.socket.Receive(flushBuffer);
                            }
                            catch(SocketException e)
                            {
                                if (!e.NativeErrorCode.Equals(10035))
                                {
                                    m_SocketsWaitForRemoved.Add(linkedGuest.socket.RemoteEndPoint.ToString());
                                }
                                continue;
                            }
                            if (recieveLength == 0)
                            {
                                m_SocketsWaitForRemoved.Add(linkedGuest.socket.RemoteEndPoint.ToString());
                                continue;
                            }
                            List<Byte> guestByts = linkedGuest.buffer;
                            int startIdx = guest.idx;
                            for(int addIdx = 0;addIdx < recieveLength;++addIdx)
                            {
                                guestByts.Add(flushBuffer[addIdx]);
                            }
                            if (guestByts.Count < 4)
                            {
                                continue;
                            }
                            if (guest.dataLength <= 0)
                            {
                                guest.dataLength = guestByts[0] + ((int)guestByts[1] << 8) + ((int)guestByts[2] << 16) + ((int)guestByts[3] << 32);
                            }
                            int dataLength = guest.dataLength;
                            if (guestByts.Count >= dataLength)
                            {
                                m_WaitingForSendMessage.Add(guest);
                            }
                        }
                        catch(Exception e)
                        {
                            
                            continue;
                        }
                    }
                    for (int idx = 0; idx < m_SocketsWaitForRemoved.Count; ++idx)
                    {
                        Console.WriteLine("Break " + m_SocketsWaitForRemoved[idx]);
                        m_Guests.Remove(m_SocketsWaitForRemoved[idx]);
                    }
                    for (int idx = 0; idx < m_WaitingForSendMessage.Count; ++idx)
                    {
                        GuestInfo guest = m_WaitingForSendMessage[idx];
                        TakeCareOfInformation(guest);
                    }
                }
                m_SocketsWaitForRemoved.Clear();
            }
        }

        private static void SendBeatData(GuestInfo guest)
        {
            guest.beatDirty = true;
            List<Byte> sendBytes = guest.buffer;
            byte buffLength = 4 + 1 + 8;
            sendBytes.Add(buffLength);
            sendBytes.Add(0);
            sendBytes.Add(0);
            sendBytes.Add(0);
            sendBytes.Add((byte)MessageType.beat);
            Int64 tempTimeStamp = m_Watch.ElapsedMilliseconds;
            guest.timeStamp = tempTimeStamp;

            for (int idx = 0; idx < 8; ++idx)
            {
                sendBytes.Add((Byte)tempTimeStamp);
                tempTimeStamp = tempTimeStamp >> 8;
            }
            guest.socket.Send(sendBytes.ToArray());
            sendBytes.Clear();
        }

        private static void TakeCareOfInformation(GuestInfo guest)
        {
            MessageType type = (MessageType) guest.buffer[4];
            switch(type)
            {
                case MessageType.beat:
                    List<Byte> buffer = guest.buffer;
                    Int64 delayTime = 0;
                    for (int idx = 0;idx < 0;++idx)
                    {
                        delayTime += buffer[idx + 5] << (idx * 8);
                    }
                    guest.timeStamp = m_Watch.ElapsedMilliseconds;
                    guest.delayTime = guest.timeStamp - delayTime;
                    Console.WriteLine( guest.socket.RemoteEndPoint.ToString() + " | " + guest.delayTime);
                    guest.beatDirty = false;
                    break;
                case MessageType.message:
                    foreach(GuestInfo reciever in m_Guests.Values)
                    {
                        reciever.socket.Send(guest.buffer.ToArray());
                    }
                    break;
            }
            guest.buffer.Clear();
            guest.dataLength = 0;
        }
    }

    class GuestInfo
    {
        public Socket socket;
        public List<Byte> buffer;
        public int idx;
        public int dataLength;
        public Int64 timeStamp;
        public Int64 delayTime;
        public bool beatDirty;
    }
}
